/**
* jsgig.image - Functionality related to the display of images.
* 
* Depends: util, jsgig.res
*/
if (!jsgig) {
  throw new Error("jsgig must be included before jsgig.image.");
}
if (!jsgig.image) { jsgig.image = (function(){
var my = {};

/**
 * Base class for image controller classes.
 * 
 * This class creates a simple HTML div with nothing in it when it is drawn.
 * Other classes may subclass this to add imagery to it by way of adding
 * subelements or changing CSS properties. It can also be instantiated and have
 * changes made to the element manually.
 * 
 * This class inherits from util.StatesMixin. It does not define a class-level
 * rule set, though subclasses do.
 */
my.Canvas = util.extend(
  util.StatesMixin,
  
  util.kw(
  function Canvas(size) {
    util.StatesMixin.call(this);
    this._size = [1,1];
    this._$el = $("<div>");
    this._element = this._$el[0];
    this._$el.css({
      overflow: "hidden",
      position: "absolute"
    });
    if (size)
      this.setSize(size);
  }),
  
  function getSize() {
    return this._size.slice();
  },
  
  function setSize(size) {
    this._size[0] = parseFloat(size[0]);
    this._size[1] = parseFloat(size[1]);
  },
  
  function getElement() {
    return this._element;
  },
  
  function get$Element() {
    return this._$el;
  },
  
  function update() {},
  
  function draw(dest, pos) {
    var el = this.getElement(),
        $el = this._$el,
        size = this.getSize();
    $el.css({
      left: pos[0] + "px",
      top: pos[1] + "px",
    });
    $el.width(size[0]).height(size[1]);
    $dest = $(dest);
    if (!($el.parent().is($dest))) {
      $el.detach();
      $dest.append(el);
    }
  },
  
  function erase() {
    if (this._$el.parent().length) {
      this._$el.detach();
    }
  }
);

/**
 * Used to create an element that displays a single, non-animated image.
 * 
 * Note that the same effect can be acheived using an Animation, but this class
 * is less resource intensive as it does not need updating.
 */ 
my.Still =
util.extend(
  my.Canvas,
  
  util.kw(
  function Still(src, size, offset, flip) {
    my.Canvas.call(this, size);
    this._src = "";
    this._offset = [0,0];
    this._flip = [false, false];
    if (src !== undefined)
      this.setSrc(src);
    if (offset !== undefined)
      this.setOffset(offset);
    if (flip !== undefined)
      this.setFlip(flip);
  }),
  
  function getSrc() {
    return this._src;
  },
  
  function setSrc(src) {
    this._src = src;
  },
  
  function getOffset() {
    return this._offset.slice();
  },
  
  function setOffset(offset) {
    this._offset[0] = parseFloat(offset[0]);
    this._offset[1] = parseFloat(offset[1]);
  },
  
  function getFlip() { return this._flip.slice(); },
  
  function setFlip(flip) {
    this._flip[0] = Boolean(flip[0]);
    this._flip[1] = Boolean(flip[1]);
  },
  
  function draw(dest, pos) {
    var el = this.getElement(),
        $el = this._$el,
        src = this.getSrc(),
        offset = this.getOffset(),
        ox = offset[0],
        oy = offset[1]
        flip = this.getFlip(),
        scalestr = "scale(" + (flip[0] ? -1 : 1) + ", " +
                              (flip[1] ? -1 : 1) + ")";
    $el.css({
      backgroundImage: src ? "url(" + src +")" : "none",
      backgroundPosition: (-ox) + "px " + (-oy) + "px",
      transform: scalestr,
      MozTransform: scalestr,
      WebkitTransform: scalestr,
      MsTransform: scalestr,
      OTransform : scalestr
    });
    my.Canvas.prototype.draw.call(this, dest, pos);
  },
  
  util.SetStorageRules([
    "src", "size", "offset", "flip"
  ])
);
 
/**
* Used to create an element that is animated using the CSS sprite technique.
* 
*   anim = new jsgig.image.Animation(
*     [src[, size[, offset, [length[, loop[, delays[, element]]]]]]]
*   );
* 
*   anim = new jsgig.image.Animation({
*     opt1 : val1,
*     opt2 : val2,
*     ...
*     optN : valN
*   });
*/
my.Animation =
util.extend(
  my.Still,
  
  util.kw(
  function Animation(src, size, cols, baseOffset, cellOffset, flip, length,
                     loop, delays, speed) {
    my.Still.call(this, {src:src, size:size, flip:flip});
    
    this._cols = 0;
    this._baseOffset = [0,0];
    this._cellOffset = [0,0];
    this._length = 1;
    this._loop = 0;
    this._delays = [1];
    this._speed = 1;
    this._started = true;
    this._frame = 0;
    this._counter = 0;
    
    this._$el.css({backgroundRepeat: "no-repeat"});
    
    if (cols)
      this.setCols(cols);
    if (baseOffset)
      this.setBaseOffset(baseOffset);
    if (cellOffset)
      this.setCellOffset(cellOffset);
    if (length !== undefined)
      this.setLength(length);
    if (loop !== undefined)
      this.setLoop(loop);
    if (delays)
      this.setDelays(delays);
    if (speed !== undefined)
      this.setSpeed(speed);
  }),
  
  function copy() {
    return new my.Animation({
      src: this.getSrc(),
      size: this.getSize(),
      cols: this.getCols(),
      baseOffset: this.getBaseOffset(),
      cellOffset: this.getCellOffset(),
      length: this.getLength(),
      loop: this.getLoop(),
      delays: this.getDelays(),
      speed: this.getSpeed(),
      flip: this.getFlip()
    });
  },
  
  /**
  * 
  */
  function start() {
    this._started = true;
  },
  
  function stop() {
    this._started = false;
  },
  
  function isStarted() {
    return this._started;
  },
  
  function reset() {
    this.setFrame(0);
    this.setCounter(0);
    this.start();
  },
  
  function getCols() {
    return this._cols;
  },
  
  function setCols(cols) {
    cols = parseInt(cols);
    if (cols < 0)
      throw new util.ValueError("cols must be >= 0");
    this._cols = cols;
  },
  
  function getBaseOffset() {
    return this._baseOffset.slice();
  },
  
  function setBaseOffset(baseOffset) {
    this._baseOffset[0] = parseFloat(baseOffset[0]);
    this._baseOffset[1] = parseFloat(baseOffset[1]);
  },
  
  function getCellOffset() { return this._cellOffset.slice(); },
  
  function setCellOffset(cellOffset) {
    this._cellOffset[0] = parseInt(cellOffset[0]);
    this._cellOffset[1] = parseInt(cellOffset[1]);
  },
  
  function getLength() {
    return this._length;
  },
  
  function setLength(length) {
    length = parseInt(length);
    if (length <= 0)
      throw new util.ValueError("Length must be >= 1.");
    this._length = length;
    while (this._delays.length < length) {
      this._delays.push(1);
    }
  },
  
  function getLoop() {
    return this._loop;
  },
  
  function setLoop(loop) {
    loop = parseInt(loop);
    if (loop < 0 || loop >= this.getLength())
      throw new util.ValueError("loop point must be >= 0 and <= length of animation.");
    this._loop = loop;
  },
  
  function getDelays() {
    return this._delays.slice();
  },
  
  function setDelays(delays) {
    delays = Array.prototype.map.call(delays, function (d) {
      d = parseFloat(d);
      if (d <= 0)
        throw new util.ValueError("delays must all be > 0.");
      return d;
    });
    while (delays.length < this.getLength()) {
      delays.push(1);
    }
    this._delays = delays;
  },
  
  function getDelay() {
    return this._delays[this._frame];
  },
  
  function getSpeed() {
    return this._speed;
  },
  
  function setSpeed(speed) {
    speed = parseFloat(speed);
    if (speed <= 0)
      throw new util.ValueError("speed must be > 0.");
    this._speed = speed;
  },
  
  function getCounter() {
    return this._counter;
  },
  
  function setCounter(counter) {
    counter = parseFloat(counter);
    if (counter < 0)
      throw new util.ValueError("counter must be >= 0");
    this._counter = counter;
  },
  
  function getFrame() {
    return this._frame;
  },
  
  function setFrame(frame) {
    frame = parseInt(frame);
    if (frame < 0)
      throw new util.ValueError("frame must be >= 0");
    this._frame = frame;
  },
  
  function update() {
    my.Still.prototype.update.call(this);
    // This algorithm currently only works with forward-moving animations,
    // which is why speed is limited to positive numbers only.
    if (this._started) {
      this._counter += this._speed;
      while (this._counter >= this._delays[this._frame]) {
        this._frame ++;
        if (this._frame >= this._length) {
          this._frame = this._loop;
        }
        this._counter %= 1;
      }
    }
  },
  
  function draw(dest, pos) {
    var size = this.getSize(),
        w = size[0],
        h = size[1],
        baseOffset = this.getBaseOffset(),
        baseOX = baseOffset[0],
        baseOY = baseOffset[1],
        cellOffset = this.getCellOffset(),
        cellOX = cellOffset[0],
        cellOY = cellOffset[1],
        cols = this.getCols(),
        length = this.getLength(),
        frame = this.getFrame(),
        finalOX =
          Math.floor(frame % (cols==0 ? Infinity : cols))
            * w + (w * cellOX) + baseOX,
        finalOY =
          ((cols == 0) ? 0 : (Math.floor(frame / cols) * h))
            + (h * cellOY) + baseOY;
    this.setOffset([finalOX, finalOY]);
    my.Still.prototype.draw.call(this, dest, pos);
  },
  
  util.SetStorageRules([
    "src",
    "size",
    "cols",
    "baseOffset",
    "cellOffset",
    "flip",
    "length",
    "loop",
    "delays",
    "speed",
    "counter",
    "frame"
  ])
);

// End of module `jsgig.image`
return my; }) (); }
